/*
 *
 *  * Unidata Platform
 *  * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 *  *
 *  * Commercial License
 *  * This version of Unidata Platform is licensed commercially and is the appropriate option for the vast majority of use cases.
 *  *
 *  * Please see the Unidata Licensing page at: https://unidata-platform.com/license/
 *  * For clarification or additional options, please contact: info@unidata-platform.com
 *  * -------
 *  * Disclaimer:
 *  * -------
 *  * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 *  * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 *  * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 *  * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 *  * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 *
 */
package org.unidata.mdm.rest.v1.dq.core.converter;

import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.unidata.mdm.core.util.SecurityUtils;
import org.unidata.mdm.dq.core.type.cleanse.CleanseFunctionExecutionScope;
import org.unidata.mdm.dq.core.type.cleanse.CleanseFunctionPortFilteringMode;
import org.unidata.mdm.dq.core.type.cleanse.CleanseFunctionPortInputType;
import org.unidata.mdm.dq.core.type.cleanse.CleanseFunctionPortValueType;
import org.unidata.mdm.dq.core.type.model.source.AbstractCleanseFunctionSource;
import org.unidata.mdm.dq.core.type.model.source.CleanseFunctionPort;
import org.unidata.mdm.rest.core.converter.CustomPropertiesConverter;
import org.unidata.mdm.rest.system.converter.Converter;
import org.unidata.mdm.rest.v1.dq.core.ro.FunctionExecutionScopeRO;
import org.unidata.mdm.rest.v1.dq.core.ro.functions.CleanseFunctionRO;
import org.unidata.mdm.rest.v1.dq.core.ro.functions.PortRO;

/**
 * The Class CleanseFunctionConverter.
 *
 * @author Michael Yashin. Created on 21.05.2015.
 */
public abstract class AbstractCleanseFunctionConverter<X extends AbstractCleanseFunctionSource<?>, Y extends CleanseFunctionRO>
    extends Converter<X, Y> {
    /**
     * Constructor.
     */
    protected AbstractCleanseFunctionConverter(Function<X, Y> in, Function<Y, X> out) {
        super(in, out);
    }

    protected static<X extends AbstractCleanseFunctionSource<?>, Y extends CleanseFunctionRO> void convert(X source, Y target) {

        target.setName(source.getName());
        target.setDisplayName(source.getDisplayName());
        target.setDescription(source.getDescription());
        target.setCreateDate(source.getCreateDate());
        target.setUpdateDate(source.getUpdateDate());
        target.setCreatedBy(source.getCreatedBy());
        target.setUpdatedBy(source.getUpdatedBy());
        target.setCustomProperties(CustomPropertiesConverter.to(source.getCustomProperties()));
        target.setGroupName(source.getGroupName());
        target.getInputPorts().addAll(convertToPorts(source.getInputPorts()));
        target.getOutputPorts().addAll(convertToPorts(source.getOutputPorts()));
        target.setSupportedExecutionScopes(source.getExecutionScopes().stream()
                    .map(scope -> FunctionExecutionScopeRO.valueOf(scope.name()))
                    .collect(Collectors.toList()));

        // State indicators, which are one-way and not part of configuration
        target.setNote(source.getNote());
        target.setSystem(source.isSystem());
        target.setReady(source.isReady());
        target.setConfigurable(source.isConfigurable());
        target.setEditable(source.isEditable());
    }

    protected static <X extends AbstractCleanseFunctionSource<?>, Y extends CleanseFunctionRO> void convert(Y source, X target) {

        String user = SecurityUtils.getCurrentUserName();
        OffsetDateTime now = OffsetDateTime.now();

        target
            .withName(source.getName())
            .withDisplayName(source.getDisplayName())
            .withDescription(source.getDescription())
            .withCreateDate(Objects.isNull(source.getCreateDate()) ? now : source.getCreateDate())
            .withCreatedBy(StringUtils.isBlank(source.getCreatedBy()) ? user : source.getCreatedBy())
            .withUpdateDate(Objects.isNull(source.getCreateDate()) ? null : now)
            .withUpdatedBy(StringUtils.isBlank(source.getCreatedBy()) ? null : user)
            .withCustomProperties(CustomPropertiesConverter.from(source.getCustomProperties()))
            .withGroupName(source.getGroupName())
            .withInputPorts(convertFromPorts(source.getInputPorts()))
            .withOutputPorts(convertFromPorts(source.getOutputPorts()))
            .withExecutionScopes(source.getSupportedExecutionScopes().stream()
                    .filter(Objects::nonNull)
                    .map(scope -> CleanseFunctionExecutionScope.valueOf(scope.name()))
                    .collect(Collectors.toList()));
    }

    /**
     * Convert ports.
     *
     * @param source
     *            the source
     * @return the list
     */
    private static List<PortRO> convertToPorts(List<CleanseFunctionPort> source) {

        if (CollectionUtils.isEmpty(source)) {
            return Collections.emptyList();
        }

        return source.stream()
                .filter(Objects::nonNull)
                .map(AbstractCleanseFunctionConverter::convertPort)
                .collect(Collectors.toList());
    }

    /**
     * Convert port.
     *
     * @param source
     *            the source
     * @return the port definition
     */
    private static PortRO convertPort(CleanseFunctionPort source) {

        if (source == null) {
            return null;
        }

        PortRO target = new PortRO();

        target.setName(source.getName());
        target.setDisplayName(source.getDisplayName());
        target.setDescription(source.getDescription());
        target.setRequired(source.isRequired());
        target.setFilteringMode(Objects.isNull(source.getFilteringMode()) ? null : source.getFilteringMode().name());
        target.setInputTypes(source.getInputTypes().stream()
                .map(CleanseFunctionPortInputType::name)
                .collect(Collectors.toList()));
        target.setValueTypes(source.getValueTypes().stream()
                .map(CleanseFunctionPortValueType::name)
                .collect(Collectors.toList()));

        return target;
    }

    /**
     * Convert ports.
     *
     * @param source
     *            the source
     * @return the list
     */
    private static List<CleanseFunctionPort> convertFromPorts(List<PortRO> source) {

        if (CollectionUtils.isEmpty(source)) {
            return Collections.emptyList();
        }

        return source.stream()
                .filter(Objects::nonNull)
                .map(AbstractCleanseFunctionConverter::convertPort)
                .collect(Collectors.toList());
    }

    /**
     * Convert port.
     *
     * @param source
     *            the source
     * @return the port definition
     */
    private static CleanseFunctionPort convertPort(PortRO source) {

        if (source == null) {
            return null;
        }

        return new CleanseFunctionPort()
            .withName(source.getName())
            .withDisplayName(source.getDisplayName())
            .withDescription(source.getDescription())
            .withRequired(source.isRequired())
            .withFilteringMode(CleanseFunctionPortFilteringMode.valueOf(source.getFilteringMode()))
            .withInputTypes(source.getInputTypes().stream()
                    .map(CleanseFunctionPortInputType::valueOf)
                    .collect(Collectors.toList()))
            .withValueTypes(source.getValueTypes().stream()
                    .map(CleanseFunctionPortValueType::valueOf)
                    .collect(Collectors.toList()));
    }
}
